package com.hillky;

import com.hillky.annotation.YrpcApi;
import com.hillky.channelhandler.handler.MethodCallHandler;
import com.hillky.channelhandler.handler.YrpcResponseEncoder;
import com.hillky.config.Configuration;
import com.hillky.core.HeartbeatDetector;
import com.hillky.channelhandler.handler.YrpcRequestDecoder;
import com.hillky.core.YrpcShutdownHook;
import com.hillky.loadbalancer.LoadBalancer;
import com.hillky.transport.message.YrpcRequest;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LoggingHandler;
import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.net.InetSocketAddress;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

@Slf4j
public class YrpcBootstrap {

    // YrpcBootstrap是个单例，我们希望每个应用程序只有一个实例
    private static final YrpcBootstrap yrpcBootstrap = new YrpcBootstrap();

    // 全局的配置中心
    private Configuration configuration;

    // 保存request对象可以在当前线程中随时获取
    public static final ThreadLocal<YrpcRequest> REQUEST_THREAD_LOCAL=new ThreadLocal<>();


    // 连接的缓存,如果使用InetSocketAddress这样的类做key，一定要看他有没有重写equals方法和toString方法
    public final static Map<InetSocketAddress, Channel> CHANNEL_CACHE = new ConcurrentHashMap<>(16);
    public final static TreeMap<Long, Channel> ANSWER_TIME_CHANNEL_CACHE = new TreeMap<>();


    // 维护已经发布且暴露的服务列表 key-> interface的全限定名  value -> ServiceConfig
    public final static Map<String, ServiceConfig<?>> SERVERS_LIST = new ConcurrentHashMap<>(16);

    //定义全局对外挂起的CompletableFuture
    public final static Map<Long, CompletableFuture<Object>> PENDING_REQUEST=new ConcurrentHashMap<>(128);


    private YrpcBootstrap() {
        // 构造启动引导程序
        configuration=new Configuration();
    }


    public static YrpcBootstrap getInstance() {
        return yrpcBootstrap;
    }

    /**
     * 用来定义当前应用的名字
     *
     * @param appName 应用的名字
     * @return this当前实例
     */
    public YrpcBootstrap application(String appName) {
        configuration.setAppName(appName);
        return this;
    }

    /**
     * 用来配置一个注册中心
     *
     * @param registryConfig 注册中心
     * @return this当前实例
     */
    public YrpcBootstrap registry(RegistryConfig registryConfig) {
        // 这里维护一个zookeeper实例，但是，如果这样写就会将zookeeper和当前工程耦合
        // 我们其实是更希望以后可以扩展更多种不同的实现
        configuration.setRegistryConfig(registryConfig);

        // 尝试使用 registryConfig 获取一个注册中心，有点工厂设计模式的意思了
        return this;
    }

    /**
     * 用来配置负载均衡器
     *
     * @param loadBalancer 注册中心
     * @return this当前实例
     */
    public YrpcBootstrap loadBalancer(LoadBalancer loadBalancer) {
        configuration.setLoadBalancer(loadBalancer);
        return this;
    }



    /**
     * 配置压缩的方式
     * @param compressType
     * @return
     */
    public YrpcBootstrap compress(String compressType) {
        configuration.setCompressType(compressType);
        if(log.isDebugEnabled()){
            log.debug("当前工程使用了：压缩算法为{}",compressType);
        }
        return this;
    }

    /**
     * 配置序列化的方式
     * @param serializeType
     * @return
     */
    public YrpcBootstrap serialize(String serializeType) {
        configuration.setSerializeType(serializeType);
        if(log.isDebugEnabled()){
            log.debug("当前工程使用了：{}协议进行序列化",serializeType);
        }
        return this;
    }

    public YrpcBootstrap reference(ReferenceConfig<?> reference) {

        // 开启对这个服务的心跳检测
        HeartbeatDetector.detectHeartbeat(reference.getInterfaceRef().getName());

        reference.setRegistry(configuration.getRegistryConfig().getRegistry());
        reference.setGroup(this.getConfiguration().getGroup());
        return this;
    }


    public YrpcBootstrap  publish(ServiceConfig<?> service) {
        //在zookeeper里面创建对应的服务节点
//        if(log.isDebugEnabled()){
//            log.debug("服务{}，已经被注册",service.getInterface().getName());
//        }
        this.configuration.getRegistryConfig().getRegistry().register(service);

        // 1、当服务调用方，通过接口、方法名、具体的方法参数列表发起调用，提供怎么知道使用哪一个实现
        // (1) new 一个  （2）spring beanFactory.getBean(Class)  (3) 自己维护映射关系
        SERVERS_LIST.put(service.getInterface().getName(), service);

        return this;
    }

    /**
     * 批量发布
     *
     * @param services 封装的需要发布的服务集合
     * @return this当前实例
     */
    public YrpcBootstrap publish(List<ServiceConfig<?>> services) {
        for (ServiceConfig<?> service : services) {
            this.publish(service);
        }
        return this;
    }


    /**
     * 启动netty服务
     */
    public void start() {
        // 注册一个关闭应用程序的钩子函数
        Runtime.getRuntime().addShutdownHook(new YrpcShutdownHook());


        // 1、创建eventLoop，老板只负责处理请求，之后会将请求分发至worker
        EventLoopGroup boss = new NioEventLoopGroup(2);
        EventLoopGroup worker = new NioEventLoopGroup(10);
        try {

            // 2、需要一个服务器引导程序
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            // 3、配置服务器
            serverBootstrap = serverBootstrap.group(boss, worker)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            socketChannel.pipeline()
                                    .addLast(new LoggingHandler())
                                    .addLast(new YrpcRequestDecoder())
                                    //进行方法调用
                            .addLast(new MethodCallHandler())
                            .addLast(new YrpcResponseEncoder());
                        }
                    });

            // 4、绑定端口
            ChannelFuture channelFuture = serverBootstrap.bind(configuration.getPort()).sync();

            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            try {
                boss.shutdownGracefully().sync();
                worker.shutdownGracefully().sync();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }


    /**
     * 扫描包 进行批量注册
     * @param packageName
     * @return
     */
    public YrpcBootstrap scan(String packageName) {

        // 需要通过packageName获取其下的所有的类的权限定名称
        List<String>classNames=getAllClassNames(packageName);
        // 通过反射获取他的接口，构建具体的实现
        List<Class<?>> classes = classNames.stream()
                .map(className -> {
                    try {
                        return Class.forName(className);
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    }
                    return null;
                }).filter(clazz -> clazz.getAnnotation(YrpcApi.class) != null)
                .collect(Collectors.toList());

        for (Class<?> clazz : classes) {
            // 获取接口
            Class<?>[] interfaces = clazz.getInterfaces();
            Object instance =null;
            try {
                instance = clazz.getConstructor().newInstance();
            } catch (InstantiationException | NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
                e.printStackTrace();
            }

            // 获取分组信息
            YrpcApi yrpcApi = clazz.getAnnotation(YrpcApi.class);
            String group = yrpcApi.group();

            for (Class<?> anInterface : interfaces) {
                ServiceConfig<Object> serviceConfig = new ServiceConfig<>();
                serviceConfig.setInterface(anInterface);
                serviceConfig.setRef(instance);
                serviceConfig.setGroup(group);

                if(log.isDebugEnabled()){
                    log.debug(" ----->已经通过包扫描将服务【{}】发布",anInterface);
                }
                //发布
                publish(serviceConfig);
            }

        }

        return  this;
    }

    private List<String> getAllClassNames(String packageName) {
        // 1. 通过packageName获得绝对路径
        // com.hillky.xxx.yyy -> E://
        String basePath=packageName.replaceAll("\\.","/");
        URL url = ClassLoader.getSystemClassLoader().getResource(basePath);
        if(url==null){
            throw new RuntimeException("包扫描时路径不存在");
        }
        String absolutePath = url.getPath();

        List<String> classNames=new ArrayList<>();
        classNames=recursionFile(absolutePath,classNames,basePath);

        return classNames;
    }

    private List<String> recursionFile(String absolutePath, List<String> classNames,String basePath) {
        // 获取文件
        File file=new File(absolutePath);
        // 判断文件是否是文件夹
        if(file.isDirectory()){
            // 找到文件夹的所有文件
            // 只要文件夹和class文件
            File[] children = file.listFiles(pathname -> pathname.isDirectory()||pathname.getPath().contains(".class"));
            if(children==null||children.length==0){
                return classNames;
            }
            for (File child : children) {
                if(child.isDirectory()){
                    recursionFile(child.getAbsolutePath(), classNames,basePath);
                }else{
                    // 文件 ---> 类的权限定名称
                    String className= getClassNameByAbsolutePath(child.getAbsolutePath(),basePath );
                    classNames.add(className);
                }
            }

        }else{
            // 文件 ---> 类的权限定名称
            String className= getClassNameByAbsolutePath(absolutePath,basePath );
            classNames.add(className);
        }

        return classNames;
    }

    private String getClassNameByAbsolutePath(String absolutePath, String basePath) {
        //C:\Users\Harris\Desktop\hillky\hillky-yrpc\yrpc-framework\yrpc-core\target\classes\com\hillky\channelhandler\handler\MethodCallHandler.class
        // -->com\hillky\channelhandler\handler\MethodCallHandler.class
        // -->com.hillky.channelhandler.handler.MethodCallHandler

        String fileName = absolutePath.substring(absolutePath.indexOf(basePath.replaceAll("/","\\\\")))
                .replaceAll("\\\\",".");
        String res=fileName.substring(0,fileName.indexOf(".class"));
        return res;

    }

    public static void main(String[] args) {
        List<String> allClassNames = YrpcBootstrap.getInstance().getAllClassNames("com.hillky");
        System.out.println(allClassNames);
    }

    public Configuration getConfiguration() {
        return configuration;
    }


    public YrpcBootstrap group(String group) {
        this.getConfiguration().setGroup(group);
        return this;
    }
}
